home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Utilities / Workspace / MonsterShelf / Source / ShelfView.m < prev   
Text File  |  1995-06-12  |  16KB  |  728 lines

  1. #import "ShelfView.h"
  2. #import "IconView.h"
  3. #import "IconDragView.h"
  4. #import "compositeBackground.h"
  5.  
  6. #import <appkit/appkit.h>
  7. #import <ansi/stdio.h>
  8. #import <ansi/string.h>
  9.  
  10.  
  11. #define    MONSTERSHELF_FILE    ".MonsterShelf"
  12. #define    GRID_ENABLE        "GridEnabled"
  13. #define    GRID_VALUE        "GridValue"
  14.  
  15. #define    MAX_GRID_VALUE        1024
  16. #define MIN_GRID_VALUE        32
  17. #define DEFAULT_GRID_STRING    "84"
  18. #define DEFAULT_GRID_VALUE    84
  19.  
  20. #define round(x,y)        (((x)+(y-1))/(y)*(y))
  21.  
  22.  
  23. @implementation ShelfView
  24.  
  25. + initialize
  26. {
  27.     static NXDefaultsVector defaults = {
  28.         {GRID_ENABLE, "NO"},
  29.     {GRID_VALUE, DEFAULT_GRID_STRING},
  30.         {NULL}
  31.     };
  32.  
  33.     NXRegisterDefaults([NXApp appName], defaults);
  34.  
  35.     return self;
  36. }
  37.  
  38.  
  39. - initFrame:(const NXRect *) aFrame
  40. {
  41.     const char        *colorString;
  42.     const char *const    types[1] = {NXFilenamePboardType};
  43.     int            screenCount;
  44.     char        *backgroundString;
  45.     NXScreen        *screens;
  46.     unsigned int    i;
  47.     
  48.     for (i=0; i<NUM_MOUNT_SLOTS; i++)
  49.         mountSlots[i] = nil;
  50.  
  51.     [super initFrame:aFrame];
  52.     [[self window] setDelegate:self];
  53.     [self registerForDraggedTypes:types count:1];
  54.  
  55.     /*
  56.      *  Determine the background color.
  57.      */
  58.     useBGColor = NO;
  59.     [NXApp getScreens:&screens count:&screenCount];
  60.     
  61.     if (screens[0].depth == NX_TwoBitGrayDepth)
  62.     backgroundString = "BWBackgroundColor";
  63.     else 
  64.     backgroundString = "BackgroundColor";
  65.  
  66.     colorString = NXGetDefaultValue("NeXT1", backgroundString);
  67.     if (colorString) {
  68.     float    r, g, b;
  69.  
  70.     sscanf(colorString, "%f %f %f", &r, &g, &b );
  71.     bgColor = NXConvertRGBAToColor(r, g, b, NX_NOALPHA);
  72.     useBGColor = YES;
  73.     }
  74.  
  75.     /*
  76.      *  Now that we've set up our view's appearance, do the rest of the
  77.      *  initialization we need to do.
  78.      */
  79.     [self readShelf];
  80.  
  81.     return self;
  82. }
  83.  
  84.  
  85. - free
  86. {
  87.     return [super free];
  88. }
  89.  
  90.  
  91. - (BOOL) acceptsFirstMouse
  92. {
  93.     return YES;
  94. }
  95.  
  96.  
  97. - (NXColor) backgroundColor
  98. {
  99.     if (useBGColor)
  100.     return bgColor;
  101.     else
  102.         return NX_COLORLTGRAY;
  103. }
  104.  
  105.  
  106. - (BOOL) isAnyViewAt:(NXPoint) aPoint besides:aView
  107. {
  108.     unsigned int    i;
  109.     unsigned int    max = [[self subviews] count];
  110.     
  111.     for (i = 0;  i < max;  i++) {
  112.     int    x = aPoint.x;
  113.     int    y = aPoint.y;
  114.         NXRect    rect;
  115.     
  116.     if ([[self subviews] objectAt:i] == aView)
  117.         continue;
  118.  
  119.     [[[self subviews] objectAt:i] getFrame:&rect];
  120.     if ((int) rect.origin.x == x && (int) rect.origin.y == y)
  121.         return YES;
  122.     }
  123.     
  124.     return NO;
  125. }
  126.  
  127.  
  128. /*
  129.  *  Align all of our IconViews on the grid.  Take care so that none overlap.
  130.  */
  131. - (void) alignSubviews
  132. {
  133.     unsigned int    i;
  134.     unsigned int    max = [[self subviews] count];
  135.     unsigned int    grid = [self gridValue];
  136.  
  137.     if (![self gridEnabled])
  138.         return;
  139.  
  140.     for (i = 0;  i < max;  i++) {
  141.     id    view = [[self subviews] objectAt:i];
  142.     NXRect    rect;
  143.     NXPoint candidatePt;
  144.     int    count;
  145.  
  146.     if (![view isKindOf:[IconView class]])
  147.         continue;
  148.  
  149.     /*
  150.      *  Make the icon the right size, and then compute the new origin.
  151.      */    
  152.     [view getFrame:&rect];
  153.     candidatePt.x = round((int) rect.origin.x, grid);
  154.     candidatePt.y = round((int) rect.origin.y, grid);
  155.  
  156.     count = bounds.size.height / grid * bounds.size.width / grid;
  157.     while (count-- > 0 && [self isAnyViewAt:candidatePt besides:view]) {
  158.         candidatePt.x += grid;
  159.         if (candidatePt.x + rect.size.width > bounds.size.width) {
  160.             candidatePt.x = 0;
  161.         candidatePt.y += grid;
  162.         if (candidatePt.y + rect.size.height > bounds.size.height)
  163.             candidatePt.y = grid;
  164.         }
  165.     }
  166.  
  167.     [view sizeTo:grid :grid];
  168.     [view moveTo:candidatePt.x :candidatePt.y];
  169.     }
  170.     
  171.     [self display];
  172. }
  173.  
  174.  
  175. - (BOOL) gridEnabled
  176. {
  177.     const char *enabled = NXGetDefaultValue([NXApp appName], GRID_ENABLE);
  178.     return (enabled && !strcmp(enabled, "YES"));
  179. }
  180.  
  181.  
  182. - (void) setGridEnabled:(BOOL) flag
  183. {
  184.     (void) NXWriteDefault([NXApp appName], GRID_ENABLE, flag ? "YES" : "NO");
  185.  
  186.     [window disableDisplay];
  187.  
  188.     [[self subviews] freeObjects];
  189.     [IconView resetCachedImages];
  190.     [self readShelf];
  191.  
  192.     [[window reenableDisplay] display];
  193. }
  194.  
  195.  
  196. - (unsigned int) gridValue
  197. {
  198.     const char *gridValue = NXGetDefaultValue([NXApp appName], GRID_VALUE);
  199.     if (gridValue)
  200.         return atoi(gridValue);
  201.     else
  202.         return DEFAULT_GRID_VALUE;
  203. }
  204.  
  205.  
  206. - setGridValue:(unsigned int) gridValue
  207. {
  208.     char gridString[20];
  209.  
  210.     if (gridValue == [self gridValue])
  211.         return self;
  212.  
  213.     if (gridValue < MIN_GRID_VALUE)
  214.         gridValue = MIN_GRID_VALUE;
  215.     else if (gridValue > MAX_GRID_VALUE)
  216.         gridValue = MAX_GRID_VALUE;
  217.  
  218.     sprintf(gridString, "%d", gridValue);
  219.     (void) NXWriteDefault([NXApp appName], GRID_VALUE, gridString);
  220.  
  221.     return self;
  222. }
  223.  
  224.  
  225. - drawSelf:(const NXRect *) rects :(int) rectCount
  226. {
  227.     if (useBGColor) {
  228.     NXSetColor(bgColor);
  229.     NXRectFill(rects);
  230.     }
  231.     else
  232.     compositeFromWorkspaceWindow(rects->origin.x, rects->origin.y,
  233.                 rects->size.width, rects->size.height);
  234.  
  235.     return self;
  236. }
  237.  
  238.  
  239. - deselectAll:sender
  240. {
  241.     [subviews makeObjectsPerform:@selector(setState:) with:(id) 0];
  242.     return NO;
  243. }
  244.  
  245.  
  246. - removeView:aView
  247. {
  248.     NXRect    viewFrame;
  249.  
  250.     [aView getFrame:&viewFrame];
  251.     [aView removeFromSuperview];
  252.     [self display:&viewFrame :1 :NO];
  253.     return self;
  254. }
  255.  
  256.  
  257. - addView:aView
  258. {
  259.     NXRect    viewFrame;
  260.  
  261.     [aView getFrame:&viewFrame];
  262.     [self addSubview:aView];
  263.     [self display:&viewFrame :1 :NO];
  264.     return self;
  265. }
  266.  
  267.  
  268. - deleteView:aView
  269. {
  270.     [self removeView:aView];
  271.     [aView free];
  272.     [self writeShelf];
  273.     return self;
  274. }
  275.  
  276.  
  277. /*
  278.  *  Return true if the point is in the area we use to get rid of views
  279.  */
  280. - (BOOL) isDeadZone:(NXPoint *) aPoint
  281. {
  282.     NXRect    goodZone = bounds;
  283.     NXInsetRect(&goodZone, 2, 2);
  284.     return !NXMouseInRect(aPoint, &goodZone, NO);
  285. }
  286.  
  287.  
  288. - (void) createViewForPath:(const char *) path at:(NXPoint *) point
  289. {
  290.     id            image = [[Application workspace] getIconForFile:path];
  291.     id            newView = [IconDragView allocFromZone:[self zone]];
  292.     NXCoord        grid = [self gridValue];
  293.     struct stat        st;
  294.     NXRect        aRect;
  295.     unsigned int    i = 0;
  296.     NXPoint        viewOrigin;
  297.  
  298.     if (stat(path, &st) < 0)
  299.         return;
  300.  
  301.     if (!point) {
  302.     /*
  303.      *  If the caller didn't know where to put the view, stick it one of
  304.      *  our default slots.
  305.      */
  306.     while (i < NUM_MOUNT_SLOTS && mountSlots[i])
  307.         ++i;
  308.     
  309.     if (i < NUM_MOUNT_SLOTS)
  310.         mountSlots[i] = newView;
  311.     
  312.     viewOrigin.x = i * [self gridValue];
  313.     viewOrigin.y = [self gridValue];
  314.  
  315.         aRect.origin = viewOrigin;
  316.     }
  317.     else 
  318.     aRect.origin = *point;
  319.  
  320.     /*
  321.      *  If the grid is on, make sure the size of the view we're about
  322.      *  to create is pegged to the grid size.
  323.      */
  324.     if ([self gridEnabled]) {
  325.         aRect.size.width = grid;
  326.     aRect.size.height = grid;
  327.     }
  328.     
  329.     [newView initFrame:&aRect image:image data:path andLength:strlen(path)+1
  330.             useSize:[self gridEnabled]];
  331.  
  332.     [self addSubview:newView];
  333.     [newView getFrame:&aRect];
  334.     [self display:&aRect :1 :NO];
  335. }
  336.  
  337.  
  338. static BOOL
  339. prefix(const char *prefix, const char *string)
  340. {
  341.     while (*prefix && *string && *prefix == *string) {
  342.         prefix++;
  343.     string++;
  344.     }
  345.     return *prefix == '\0';
  346. }
  347.  
  348.  
  349. - (void) removeViewForPath:(const char *) fullPath
  350. {
  351.     int        i = [[self subviews] count];
  352.  
  353.     while (i > 0) {
  354.         char        *path;
  355.     unsigned int    len;
  356.     id        view = [[self subviews] objectAt:i];
  357.     if ([view isKindOf:[IconView class]]) {
  358.         [view getData:(void *) &path andLength:&len];
  359.         if (prefix(fullPath, path)) {
  360.             unsigned int    j=0;
  361.  
  362.         while (j < NUM_MOUNT_SLOTS && mountSlots[j] != view)
  363.             ++j;
  364.         if (j < NUM_MOUNT_SLOTS)
  365.             mountSlots[j] = nil;
  366.  
  367.             [self deleteView:view];
  368.         }
  369.     }
  370.     i--;
  371.     }
  372. }
  373.  
  374.  
  375. /*
  376.  *  Find the right position for the new image, based on the grid and the
  377.  *  mouse's location.
  378.  */
  379. - (NXPoint) viewLocationForContext:(id <NXDraggingInfo>)dragContext
  380. {
  381.     NXPoint        newLoc;
  382.     unsigned int    grid = [self gridValue];
  383.     NXPoint        imagePt = [dragContext draggedImageLocation];
  384.     NXPoint        mousePt = [dragContext draggingLocation];
  385.     NXPoint        imageOffset;
  386.     
  387.     [draggedView getImagePoint:&imageOffset andHilitePoint:NULL];
  388.  
  389.     if ([self gridEnabled]) {
  390.         NXRect    rect;
  391.     [draggedView getFrame:&rect];
  392.     
  393.         newLoc.x = mousePt.x - ((int) mousePt.x % grid) +
  394.              (grid - rect.size.width) / 2;
  395.     newLoc.y = mousePt.y - (int) mousePt.y % grid;
  396.     }
  397.     else {
  398.     newLoc.x = imagePt.x - imageOffset.x;
  399.     newLoc.y = imagePt.y - imageOffset.y;
  400.     }
  401.     return newLoc;
  402. }
  403.  
  404.  
  405. /*
  406.  *  Make a ghost image to indicate that we're really a destination. 
  407.  */
  408. - (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
  409. {
  410.     NXPoint    newLoc;
  411.     NXSize    aSize, *sizePtr = NULL;
  412.     
  413.     if ([self gridEnabled]) {
  414.         aSize.width = [self gridValue];
  415.     aSize.height = aSize.width;
  416.     sizePtr = &aSize;
  417.     }
  418.  
  419.     draggedView = [[IconView allocFromZone:[self zone]]
  420.             initFromDragContext:sender andSize:sizePtr];
  421.  
  422.     if ([self gridEnabled]) {
  423.     newLoc = [self viewLocationForContext:sender];
  424.     [draggedView moveTo:newLoc.x :newLoc.y];
  425.     
  426.     [draggedView setGhost:YES];
  427.     [self addView:draggedView];
  428.     }
  429.  
  430.     return NX_DragOperationAll;
  431. }
  432.  
  433.  
  434. /*
  435.  *  Move the dragged image, but only do it if we need to (that is, if the
  436.  *  mouse moved to a new grid cell).
  437.  */
  438. - (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
  439. {
  440.     NXPoint    mouseLoc;
  441.     NXPoint    newLoc = [self viewLocationForContext:sender];
  442.     NXRect    aFrame;
  443.  
  444.     if (![self gridEnabled])
  445.     return NX_DragOperationAll;
  446.  
  447.     /*
  448.      *  If the icon was dragged off the edge, hide it somewhere!
  449.      */
  450.     mouseLoc = [sender draggingLocation];
  451.     if ([self isDeadZone:&mouseLoc]) {
  452.         newLoc.x = -100;
  453.     newLoc.y = -100;
  454.     }
  455.  
  456.     [draggedView getFrame:&aFrame];
  457.     if (aFrame.origin.x != newLoc.x || aFrame.origin.y != newLoc.y) {
  458.         [draggedView moveTo:newLoc.x :newLoc.y];
  459.  
  460.         [self display:&aFrame :1 :NO];        /* erase old */
  461.  
  462.          aFrame.origin = newLoc;
  463.         [self display:&aFrame :1 :NO];        /* draw new */
  464.     }
  465.  
  466.     return NX_DragOperationAll;
  467. }
  468.  
  469.  
  470. /*
  471.  *  Get rid of the resources we used to drag the image around.
  472.  */
  473. - draggingExited:(id <NXDraggingInfo>)sender
  474. {
  475.     [self removeView:draggedView];
  476.     [draggedView free];
  477.  
  478.     return self;
  479. }
  480.  
  481.  
  482. /*
  483.  *  Eat the result...
  484.  */
  485. - (BOOL) prepareForDragOperation:sender
  486. {
  487.     NXPoint    mouseLoc;
  488.     NXRect    aFrame;
  489.     NXPoint    newLoc = [self viewLocationForContext:sender];
  490.  
  491.     /*
  492.      *  If the dragged item landed in the dead zone, get rid of it.  If
  493.      *  the dragged item originated with us, we "accept" the image to tell
  494.      *  the source to free it.
  495.      */
  496.     mouseLoc = [sender draggingLocation];
  497.     if ([self isDeadZone:&mouseLoc]) {
  498.     [draggedView getFrame:&aFrame];
  499.     [draggedView free];
  500.     [self display:&aFrame :1 :NO];
  501.         return [sender isDraggingSourceLocal];
  502.     }
  503.  
  504.     /*
  505.      *  Turn the dragged IconView into an IconDragView that's actually
  506.      *  capable of being a drag destination, too.
  507.      */
  508.     [draggedView moveTo:newLoc.x :newLoc.y];
  509.     [self addView:[IconDragView copyIconView:draggedView]];
  510.     [draggedView free];
  511.     
  512.     return YES;
  513. }
  514.  
  515.  
  516. - (BOOL) performDragOperation:sender
  517. {
  518.     return YES;
  519. }
  520.  
  521.  
  522. /*
  523.  *  Actually write the stuff way down here.  It's completely at the end
  524.  *  of the operation, so a slow write won't hose the UI.
  525.  */    
  526. - concludeDragOperation:(id <NXDraggingInfo>)sender
  527. {
  528.     [self writeShelf];
  529.     return self;
  530. }
  531.  
  532.  
  533. /*
  534.  *  Be a drag source, too
  535.  */
  536. - setDragView:aView onEvent:(NXEvent *) e withOffset:(NXPoint *) offset atLocation:(const NXPoint *) location
  537. {
  538.     id            pb = [Pasteboard newName:NXDragPboard];
  539.     void        *data;
  540.     unsigned int    length;
  541.     NXPoint        myLoc;
  542.  
  543.     /*
  544.      *  Initiate a drag operation.  Copy stuff into the pasteboard,
  545.      *  then start dragging.  To simplify matters elsewhere, we try
  546.      *  to make a local dragging operation look just like a non-local
  547.      *  one.
  548.      */
  549.     dragSourceView = aView;
  550.     keepSourceOnShelf = (e->flags & NX_ALTERNATEMASK) ? YES : NO;
  551.  
  552.     [aView getData:&data andLength:&length];
  553.     [pb declareTypes:&NXFilenamePboardType num:1 owner:nil];
  554.     [pb writeType:NXFilenamePboardType data:data length:length];
  555.  
  556.     myLoc = *location;
  557.     [aView convertPoint:&myLoc toView:self];
  558.     [self dragImage:[aView image] at:&myLoc
  559.             offset:offset event:e pasteboard:pb
  560.             source:self slideBack:YES];
  561.  
  562.     return self;
  563. }
  564.  
  565.  
  566. - draggedImage:(NXImage *)image beganAt:(NXPoint *)screenPoint
  567. {
  568.     NXRect    theFrame;
  569.  
  570.     [dragSourceView getFrame:&theFrame];
  571.     [self display:&theFrame :1 :NO];
  572.     if (!keepSourceOnShelf)
  573.     [self removeView:dragSourceView];
  574.  
  575.     return self;
  576. }
  577.  
  578.  
  579. /*
  580.  *  A drag operation, with us as the source, finished.  If it was an
  581.  *  unsuccessful drag then, put the source back!  If it was a successful
  582.  *  drag, and we weren't the destination, then remove the thing from the
  583.  *  shelf.
  584.  */
  585. - draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint
  586.      deposited:(BOOL)didDeposit
  587. {
  588.     char        *path;
  589.     unsigned int    len;
  590.     struct stat        st;
  591.  
  592.     /*
  593.      *  Check to see if we should keep the source dir on the shelf.  We
  594.      *  do this if the keepSourceOnShelf flag is set, and if the file
  595.      *  under the icon still exists.
  596.      */
  597.     [dragSourceView getData:(void **) &path andLength:&len];
  598.     if (keepSourceOnShelf && path && stat(path, &st) == 0) {
  599.     keepSourceOnShelf = NO;
  600.     return self;
  601.     }
  602.     
  603.     /*
  604.      *  The source isn't on the screen, so either get rid of the source, or
  605.      *  put it back.
  606.      */
  607.     if (didDeposit)
  608.         [self deleteView:dragSourceView];
  609.     else
  610.         [self addView:dragSourceView];
  611.  
  612.     return self;
  613. }
  614.  
  615.  
  616. - (NXDragOperation) draggingSourceOperationMaskForLocal:(BOOL)flag
  617. {
  618.     return NX_DragOperationAll;
  619. }
  620.  
  621.  
  622. /*
  623.  *  Open the shelf file.
  624.  */
  625. - (FILE *) openShelfFor:(char *) how
  626. {
  627.     char    path[MAXPATHLEN];
  628.  
  629.     sprintf(path, "%s/%s", NXHomeDirectory(), MONSTERSHELF_FILE);
  630.     return fopen(path, how);
  631. }
  632.  
  633.  
  634. /*
  635.  *  Close it.
  636.  */
  637. - closeShelf:(FILE *) file
  638. {
  639.     fclose(file);
  640.     return self;
  641. }
  642.  
  643.  
  644. /*
  645.  *  Read the contents of the shelf in from a file.  The file's format consists
  646.  *  of lines of the form:
  647.  *
  648.  *    x y path
  649.  *
  650.  *  where the two numbers x,y specify the origin of the particular view on the
  651.  *  shelf, and path specifies the path to the workspace.  Somewhat bogusly,
  652.  *  we assume the path starts at character 14.
  653.  */
  654. - readShelf
  655. {
  656.     FILE    *file;
  657.     char    line[MAXPATHLEN + 30];
  658.     char    *path;
  659.     NXPoint    point;
  660.  
  661.     file = [self openShelfFor:"r"];
  662.     if (file == NULL)
  663.     return self;
  664.  
  665.     while (fgets(line, sizeof(line), file)) {
  666.  
  667.     /*
  668.      *  Parse the line in the shelf.  It's too bad that we can't use
  669.      *  sscanf to parse the whole line!
  670.      */
  671.     sscanf(line, "%f %f", &point.x, &point.y);
  672.  
  673.     /* file string starts after second number, char 14 */
  674.     if (strlen(line) > 14) {
  675.         path = line + 14;
  676.         if (rindex(path, '\n') != NULL)
  677.             *rindex(path, '\n') = '\0';
  678.     }
  679.     else
  680.         continue;
  681.  
  682.     /*
  683.      *  Make a spot for this guy...
  684.      */
  685.     [self createViewForPath:path at:&point];
  686.     }
  687.     
  688.     [self closeShelf:file];
  689.     if ([self gridEnabled])
  690.     [self alignSubviews];
  691.  
  692.     return self;
  693. }
  694.  
  695.  
  696. /*
  697.  *  Write the contents of the shelf out to the shelf file.
  698.  */
  699. - writeShelf
  700. {
  701.     FILE    *file;
  702.     NXRect    rect;
  703.     int        i;
  704.  
  705.     file = [self openShelfFor:"w"];
  706.     if (file == NULL)
  707.     return self;
  708.  
  709.     for (i = 0;  i < [[self subviews] count];  i++) {
  710.     id        view = [[self subviews] objectAt:i];
  711.     unsigned int    length;
  712.     char        *path;
  713.  
  714.     if (![view isKindOf:[IconView class]] || [view isOnRemovableMedia])
  715.         continue;
  716.     
  717.     [view getData:(void **) &path andLength:&length];
  718.     [view getFrame:&rect];
  719.  
  720.     fprintf(file, "%6.0f %6.0f %s\n", rect.origin.x, rect.origin.y, path);
  721.     }
  722.     
  723.     [self closeShelf:file];
  724.     return self;
  725. }
  726.  
  727. @end
  728.